import path from 'path'
import config from '@/config'
import processConfig from './process'
import { readConfigFile } from './read'
import { resolvePath, checkPath } from '@/utils/path'
import type {
  ServerConfig,
  ServerProjectConfig,
  ParsedServerConfig,
  SendConfig,
  ParsedSendConfig,
  AppendFileType
} from '@/types/configFile'
import { validHost } from '@/utils/validate'
import ignore from 'ignore'
import { relative } from '@/utils/ignore'

// 匹配密码别名
const matchPasswordAliasRegExp = /@.+/

export function readConfigFileWithServer(configFilePath: string = config.cwd) {
  const configNames = config.configName.server
  const { data, readPath } = readConfigFile<ServerConfig>(configFilePath, configNames)
  const fileName = path.basename(readPath)
  const dirPath = path.dirname(readPath)
  const names: string[] = []

  processConfig(fileName, data, {
    port: {
      type: 'number',
      defaultValue: config.default.port
    },
    passwordAlias: {
      type: 'object',
      items: {
        type: 'string'
      }
    },
    defaultCachePath: {
      type: 'string',
      defaultValue: config.defaultCacheDir,
      format(v) {
        if (v) {
          const p = resolvePath(dirPath, v)
          const res = checkPath(p, 'dir')

          if (!res.exist) {
            throw new Error(`${p} not found`)
          }

          if (res.typeError) {
            throw new Error(`${p} is not a directory`)
          }

          return p
        }
      }
    },
    forceUseDefaultCachePath: {
      type: 'boolean',
      defaultValue: config.default.forceUseDefaultCachePath,
      validator(v) {
        if (v === true && !data.defaultCachePath) {
          return 'Please set "defaultCachePath" before opening "forceUseDefaultCachePath"'
        }
        return true
      }
    },
    backup: {
      type: 'boolean',
      defaultValue: config.default.backup
    },
    maxBackupCount: {
      type: 'number',
      defaultValue: config.default.maxBackupCount
    },
    project: {
      type: ['array', 'object'],
      required: true,
      validator(v) {
        if (Array.isArray(v)) {
          if (v.length === 0) return 'Include at least one project'
        } else {
          if (!v) return 'Cannot be empty'
        }
        return true
      },
      format(v) {
        const arr = Array.isArray(v) ? v : [v]

        return arr.map((a) => {
          let root = a.cachePath as string
          if (data.forceUseDefaultCachePath || a.cachePath === null) {
            root = path.join(data.defaultCachePath as string, a.name)
          }

          // @ts-ignore
          a.cache = {
            root,
            temp: path.join(root, config.cache.names.temp),
            backup: path.join(root, config.cache.names.backup),
            log: path.join(root, config.cache.names.log),
            hashMap: path.join(root, config.cache.names.hashMap)
          }

          return a
        })
      },
      children: {
        name: {
          type: 'string',
          required: true,
          validator(v) {
            if (names.includes(v)) {
              return `${v} repeat`
            }
            names.push(v)
            return true
          }
        },
        path: {
          type: 'string',
          required: true,
          format(v) {
            const absolute = resolvePath(dirPath, v)
            const res = checkPath(absolute, 'dir')
            if (!res.exist) {
              throw new Error(`${absolute} is not a exist`)
            } else if (res.typeError) {
              throw new Error(`${absolute} is not a directory`)
            }
            return absolute
          }
        },
        password: {
          type: ['string', 'array', 'object'],
          required: true,
          items: {
            type: 'string',
            validator(v) {
              if (typeof v === 'string' && !matchPasswordAliasRegExp.test(v) && v.length < 6) {
                return 'The password length cannot be less than 6'
              }
              return true
            }
          },
          validator(v) {
            if (typeof v === 'string' && !matchPasswordAliasRegExp.test(v) && v.length < 6) {
              return 'The password length cannot be less than 6'
            }
            return true
          },
          format(v) {
            let res = []
            if (typeof v === 'string') {
              res = [v]
            } else if (Array.isArray(v)) {
              res = v
            } else {
              res = Object.values(v)
            }

            return res.map((a) => {
              if (matchPasswordAliasRegExp.test(a)) {
                if (data.passwordAlias) {
                  const k = a.substring(1)
                  if (data.passwordAlias[k]) {
                    return data.passwordAlias[k]
                  } else {
                    throw new Error(`No password alias exists: ${a}`)
                  }
                } else {
                  throw new Error('Password alias not configured')
                }
              } else {
                return a
              }
            })
          }
        },
        before: {
          type: 'function'
        },
        after: {
          type: 'function'
        },
        error: {
          type: 'function'
        },
        cachePath: {
          type: ['string', 'null'],
          defaultValue(v: ServerProjectConfig) {
            return path.join(v.path, config.default.cacheDir)
          }
        }
      }
    }
  })

  return data as ParsedServerConfig
}

export function readConfigFileWithSend(configFilePath: string = config.cwd) {
  const configNames = config.configName.send
  const { data, readPath } = readConfigFile<SendConfig>(configFilePath, configNames)
  const fileName = path.basename(readPath)
  const dirPath = path.dirname(readPath)
  const names: string[] = []

  processConfig(fileName, data, {
    project: {
      required: true,
      type: ['array', 'object'],
      validator(v) {
        if (Array.isArray(v) && v.length === 0) {
          return 'When "project" is an array, configure at least one'
        }
        return true
      },
      format(v) {
        if (Array.isArray(v)) {
          return v
        } else {
          return [v]
        }
      },
      children: {
        name: {
          required: true,
          type: 'string',
          validator: (v) => {
            if (names.includes(v)) {
              return `"${v}" already exists`
            }
            names.push(v)
            return true
          }
        },
        host: {
          required: true,
          type: 'string',
          validator(v) {
            if (!validHost(v)) {
              return 'Please fill in the correct host address'
            }
            return true
          },
          format(v) {
            return v.trim()
          }
        },
        port: {
          defaultValue: config.default.port,
          type: 'number',
          validator(v) {
            if (v < 0 || v > 65535) {
              return 'The port number must be between 0 and 65535'
            }
            return true
          }
        },
        path: {
          required: true,
          type: 'string',
          format(v) {
            const absolute = resolvePath(dirPath, v)
            const res = checkPath(absolute, 'dir')
            if (!res.exist) {
              throw new Error(`${absolute} is not a exist`)
            } else if (res.typeError) {
              throw new Error(`${absolute} is not a directory`)
            }
            return absolute
          }
        },
        ignore: {
          type: ['array'],
          format(v) {
            const ig = ignore()
            ig.add(config.default.sendIgnore)
            ig.add(relative(readPath, config.cwd))
            if (v) {
              ig.add(v)
            }
            return ig
          },
          defaultValue: () => ignore().add(config.default.sendIgnore),
          items: {
            type: 'string'
          }
        },
        // packageJson: {
        //   type: 'object',
        //   children: {
        //     removeProperty: {
        //       type: 'array'
        //     },
        //     reservedProperty: {
        //       type: 'array'
        //     }
        //   }
        // },
        appendFiles: {
          type: 'array',
          items: {
            type: ['object', 'string'],
            children: {
              from: {
                type: 'string',
                required: true
              },
              to: {
                type: 'string'
              },
              overwrite: {
                type: 'boolean'
              }
            },
            format(v) {
              if (typeof v === 'string') {
                v = { from: v }
              }
              v = Object.assign({}, defaultAppendFile, v)
              v.from = resolvePath(dirPath, v.from)
              return v
            }
          }
        },
        password: {
          type: 'string'
        }
      }
    }
  })

  return data as any as ParsedSendConfig
}

const defaultAppendFile: AppendFileType = {
  from: '.',
  to: '.',
  overwrite: false,
  ignore: []
}
